package ferp.center.server.backend;

import com.google.appengine.api.taskqueue.TaskHandle;
import ferp.center.server.Action;
import ferp.center.server.entity.Profile;
import ferp.center.server.entity.Range;
import ferp.center.server.entity.RangeTree;
import ferp.center.server.entity.Rating;
import ferp.center.server.ranking.Ranker;
import ferp.center.server.task.TaskRangeTreeRemoveRating;
import ferp.center.server.task.TaskRangeTreeUpdate;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;

/**
 * User: olegs
 * Date: 6/2/13 8:25 PM
 */
public class LeaderboardTest extends HttpServlet
{
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
  {
    TaskHandle t = Ranker.queue().add(withUrl("/lbt"));

    System.out.println("queued leaderboard test task, handle=" + t.toString());
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
  {
    Action.execute(request, new Task());
  }

  private static class Task extends Action.Task
  {
    @Override
    protected void run(EntityManager em, HttpServletRequest request) throws IOException
    {
      // wipe everything
      wipe(em, Range.class);
      wipe(em, RangeTree.class);
      wipe(em, Profile.class);

      // setup profiles and ratings
      for (int i = 0; i < TEST_SIZE; ++i)
      {
        final Profile p = new Profile();

        // create profile
        p.setName("P" + i);
        p.setCreated(new Date(System.currentTimeMillis()));
        p.setAccessed(p.getCreated());
        p.setLocale("ru");
        p.setLastPlayedPoolSize(10);

        // create 1 rating per profile
        Rating r = new Rating();

        p.getRatings().add(r);

        r.setProfile(p);
        r.setValue(random.nextInt(MAX_SCORE) * (random.nextInt(2) > 0 ? 1 : -1));
        r.setConfiguration(CONFIGURATION);

        // save created profile and rating
        persist(em, p);

        // add the rating to the priority queue
        ratings.addAll(p.getRatings());
        progress(i, 10);
      }

      System.out.println("\ncreated test set of size " + TEST_SIZE);

      for (Rating r : ratings)
        TaskRangeTreeUpdate.queue(r);

      // wait for the last rating
      for (Rating last = ratings.getLast(); em.find(Rating.class, last.getKey()).getRange() == null; sleep(5000))
        /**/;

      dump(em);

      while (ratings.size() > 5)
      {
        int i = random.nextInt(ratings.size());
        Rating r = em.find(Rating.class, ratings.get(i).getKey());
        System.out.println("removing profile " + r.getProfile().getName());

        try
        {
          em.clear();
          remove(em, r.getProfile());
        }
        catch (Exception e)
        {
          e.printStackTrace();
          return;
        }

        ratings.remove(i);
        TaskRangeTreeRemoveRating.queue(r);
      }

      while (true)
      {
        sleep(10000);
        dump(em);
      }
    }

    private static final class RatingComparator implements Comparator<Rating>
    {
      @Override
      public int compare(Rating o1, Rating o2) {return (int)(o2.getValue() - o1.getValue());}
    }

    private final LinkedList<Rating> ratings = new LinkedList<Rating>();

    private void dump(EntityManager em)
    {
      List<List<Rating>> top = Ranker.top(em, CONFIGURATION, 10);
      List<Rating> test = new LinkedList<Rating>();

      for (List<Rating> position : top)
        for (Rating r : position)
          test.add(r);

      Collections.sort(ratings, new RatingComparator());
      for (Rating r : ratings)
        System.out.print(r.getValue() + " ");
      System.out.println();
      for (Rating r : test)
        System.out.print(r.getValue() + " ");
      System.out.println();
    }
  }

/*
  private void setup(EntityManager em) throws IOException
  {
    clean(em);

    System.out.println("setting up RTT test environment...");



    for (int j = 0; j < RATINGS_PER_PROFILE; ++j)
    {
      createRating(p);

      progress(j, 10);
    }

//    EntityTransaction tx = em.getTransaction();

//    tx.begin();
    em.persist(p);
    em.refresh(p);
//    tx.commit();

    System.out.println("created profile id=" + p.getKey().getId() + ", ratings created: " + RATINGS_PER_PROFILE);
  }

  private static Profile getTestProfile(EntityManager em)
  {
*/
/*
    //Query q = em.createQuery("SELECT p FROM Profile p WHERE p.name = ?1");
    Query q = em.createQuery("SELECT p FROM Profile p WHERE p.device is null");

    //q.setParameter(1, PROFILE);

    List<Profile> profiles = q.getResultList();

    System.out.println("profile list size " + profiles.size());

    //return profiles.size() > 0 ? profiles.get(0) : null;
    return null;
*//*

    return em.find(Profile.class, 3139005);
  }

  private void clean(EntityManager em)
  {
    cleanTree(em);

    Profile p = getTestProfile(em);

    if (p != null)
    {
      em.refresh(p);
      em.remove(p);

      System.out.println("test profile has been removed");
    }
    else
      System.out.println("test profile not found");
  }

  private void cleanTree(EntityManager em)
  {
    int count = 0;

    wipe(em, Range.class);
    wipe(em, RangeTree.class);

    Profile p = getTestProfile(em);

    if (p != null)
    {
      for (Rating r : p.getRatings())
      {
        r.setRange(null);
        progress(count++, 10);
      }

      System.out.println(count + " ratings have been reset");
    }
  }

  private void createRating(Profile p)
  {
    Rating r = new Rating();

    p.getRatings().add(r);

    r.setProfile(p);
    r.setValue((long)random.nextInt(MAX_SCORE) * (random.nextInt(2) > 0 ? 1 : -1));

*/
/*
    StringBuilder sb = new StringBuilder(5);
    sb.append(random.nextInt(Settings.GameFlavor.values().length));
    sb.append(random.nextInt(Settings.PassingProgression.values().length));
    sb.append(random.nextInt(Settings.PassingExit.values().length));
    sb.append(random.nextInt(Settings.Level.values().length));
    sb.append(random.nextInt(Settings.Level.values().length));

    r.setConfiguration(sb.toString());
*//*

    r.setConfiguration(configurations[random.nextInt(configurations.length)]);
  }

  private void build(EntityManager em)
  {
    int count = 0;

    cleanTree(em);

    for (Rating r : getTestProfile(em).getRatings())
    {
      r.setRange(null);

      TaskRangeTreeUpdate.queue(r);

*/
/*
      try {
        Thread.sleep(1000);
      }
      catch (InterruptedException e)
      {
      }
*//*

    }
  }

  private void validate(EntityManager em)
  {
    Collection<Rating> ratings = getTestProfile(em).getRatings();
    TreeSet<Long> ts = new TreeSet<Long>(Collections.reverseOrder());

    System.out.println("validating " + ratings.size() + " ratings...");

    for (Rating r : ratings)
      ts.add(r.getValue());

    Long[] sorted = ts.toArray(new Long[ts.size()]);
    int count = 0;

    for (Rating r : ratings)
    {
      long rank = Ranker.rank(em, r);

      progress(count++, 10);

      if (sorted[(int)rank] != r.getValue())
      {
        System.err.println("error: rank #" + rank + ", rating " + r.getValue());

        return;
      }
    }
  }
*/

  private static final int TEST_SIZE = 100;
  private static final int MAX_SCORE = (int)Math.round(Math.sqrt((double)TEST_SIZE));
  private static final String CONFIGURATION = "00";
  private static final Random random = new Random();

/*
  private static abstract class Transaction
  {
    public Transaction(EntityManager em)
    {
      EntityTransaction tx = em.getTransaction();

      tx.begin();
      run(em);
      tx.commit();
    }

    protected abstract void run(EntityManager em);
  }
*/

  private static <T> void wipe(EntityManager em, Class<T> c)
  {
    List<T> list = em.createQuery("SELECT t FROM " + c.getSimpleName() + " t").getResultList();
    int count = 0;

    for (final T t : list)
    {
      remove(em, t);
      progress(count++, 10);
    }

    System.out.println("\n" + count + " " + c.getSimpleName().toLowerCase() + "s removed");
  }

  private static <T> void persist(EntityManager em, T t)
  {
    EntityTransaction tx = em.getTransaction();

    tx.begin();
    em.persist(t);
    tx.commit();
  }

  private static <T> void remove(EntityManager em, T t)
  {
    EntityTransaction tx = em.getTransaction();

    tx.begin();
    em.remove(t);
    tx.commit();
  }

  private static void sleep(long ms)
  {
    try
    {
      Thread.sleep(ms);
    }
    catch (InterruptedException e)
    {
      e.printStackTrace();
    }
  }

  private static void progress(int count, int mod)
  {
    if (count % mod == 0)
      System.out.print('.');
  }
}
